package com.mai4j.maiadmin.configure;

import com.github.wnameless.json.unflattener.JsonUnflattener;
import com.google.gson.Gson;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;
import com.mai4j.maiadmin.exception.BusinessException;
import com.mai4j.maiadmin.model.RespCode;
import com.mai4j.maiadmin.model.http.response.ExceptionResp;
import com.mai4j.maiadmin.model.http.response.NotValidResp;
import com.mai4j.maiadmin.support.I18nProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 全局异常处理
 */
@RestControllerAdvice(annotations = RestController.class)
@Slf4j
public class GlobalRestExceptionHandler {

    /**
     * 数据校验异常
     *
     * @param e e
     * @return NotValidResp
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<NotValidResp> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.warn(e.getMessage());
        BindingResult bindingResult = e.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        NotValidResp body = new NotValidResp().setCode(RespCode.INVALID_ARG.getCode()).setMessage(I18nProvider.get("text.not.valid"));
        body.setErrors(new Gson()
                .fromJson(JsonUnflattener.unflatten(
                                fieldErrors
                                        .stream()
                                        .collect(
                                                Collectors.toMap(FieldError::getField,
                                                        f -> Optional.ofNullable(f.getDefaultMessage()).orElse(""),
                                                        (v1, v2) -> v1))),
                        new TypeToken<LinkedTreeMap<String, Object>>() {
                        }.getType()));
        return ResponseEntity.status(HttpStatus.OK).body(body);
    }

    /**
     * 业务异常
     *
     * @param e e
     * @return ExceptionResp
     */
    @ExceptionHandler(value = BusinessException.class)
    public ResponseEntity<ExceptionResp> handleBusinessException(BusinessException e) {
        log.warn(e.getMessage());
        String message = e.isKey() ? I18nProvider.get(e.getMessage()) : e.getMessage();
        return ResponseEntity.status(HttpStatus.OK).body(
                new ExceptionResp()
                        .setCode(e.getCode().getCode())
                        .setSubCode(e.getCode().getCode())
                        .setMessage(message));
    }

    /**
     * SQL异常
     *
     * @param e e
     * @return ExceptionResp
     */
    @ExceptionHandler(value = SQLException.class)
    public ResponseEntity<ExceptionResp> handleSQLException(SQLException e) {
        log.error(e.getMessage(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
                new ExceptionResp()
                        .setCode(RespCode.INTERNAL_ERROR.getCode())
                        .setSubCode(RespCode.INTERNAL_SERVER_ERROR.getCode())
                        .setMessage(I18nProvider.get("text.internal_server_error")));
    }

    /**
     * HttpMessageConversionException
     *
     * @param e e
     * @return ExceptionResp
     */
    @ExceptionHandler(value = HttpMessageConversionException.class)
    public ResponseEntity<ExceptionResp> handleHttpMessageConversionException(HttpMessageConversionException e) {
        log.warn(e.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
                new ExceptionResp()
                        .setCode(RespCode.INTERNAL_ERROR.getCode())
                        .setSubCode(RespCode.INTERNAL_SERVER_ERROR.getCode())
                        .setMessage(I18nProvider.get("text.internal_server_error")));
    }

    /**
     * 运行异常
     *
     * @param e e
     * @return ExceptionResp
     */
    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<ExceptionResp> handleRuntimeException(RuntimeException e) {
        log.error(e.getMessage(), e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
                new ExceptionResp()
                        .setCode(RespCode.INTERNAL_ERROR.getCode())
                        .setSubCode(RespCode.INTERNAL_SERVER_ERROR.getCode())
                        .setMessage(I18nProvider.get("text.internal_server_error")));
    }

    /**
     * 权限异常
     *
     * @param e e
     * @return ExceptionResp
     */
    @ExceptionHandler(value = AccessDeniedException.class)
    public ResponseEntity<ExceptionResp> handleAccessDeniedException(AccessDeniedException e) {
        log.warn(e.getMessage());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
                new ExceptionResp()
                        .setCode(RespCode.INTERNAL_ERROR.getCode())
                        .setSubCode(RespCode.INTERNAL_SERVER_ERROR.getCode())
                        .setMessage(I18nProvider.get("text.access_denied")));
    }
}